SPB Git

spb/autoscholar

Public
1.6 KB · 41 lines typescript
1import { AppError, notFound } from "@autoscholar/core";2import { artifacts } from "@autoscholar/db";3import { and, desc, eq } from "drizzle-orm";4import { handler } from "@/lib/server/api";5import { db, storage } from "@/lib/server/db";6import { requireAdmin } from "@/lib/server/session";78export const runtime = "nodejs";910/** Private artifact download for admins (latest version unless ?version=n). */11export const GET = handler(async (req, { params }) => {12  await requireAdmin(req);13  const { id, path } = (await params) as { id: string; path: string[] };14  const rel = path.map(decodeURIComponent).join("/");15  if (rel.includes("..")) throw new AppError("invalid", "bad path", 400);16  const url = new URL(req.url);17  const version = url.searchParams.get("version");18  const conds = [eq(artifacts.runId, id), eq(artifacts.path, rel)];19  if (version) conds.push(eq(artifacts.version, Number(version)));20  const [row] = await db()21    .select()22    .from(artifacts)23    .where(and(...conds))24    .orderBy(desc(artifacts.version))25    .limit(1);26  if (!row) throw notFound("artifact");27  const buf = await storage().get(row.storageKey);28  const inline =29    /^(application\/pdf|image\/|text\/)/.test(row.contentType) && url.searchParams.get("download") !== "1";30  return new Response(new Uint8Array(buf), {31    headers: {32      "content-type": row.contentType,33      "content-length": String(buf.length),34      "content-disposition": `${inline ? "inline" : "attachment"}; filename="${rel.split("/").pop()}"`,35      "x-artifact-sha256": row.sha256,36      "cache-control": "private, no-store",37      "content-security-policy": "sandbox",38    },39  });40});41
Files · main
Menu

Theme